using System;
using System.Collections.Generic;
using PropertyExpression.Common;

namespace PropertyExpression.ReactiveRelation
{
    public class FuncCache
    {
        internal FuncCache()
        {
            //no-op
        }

        private readonly Dictionary<object, Dictionary<object, object>> cacheByArguments =
            new Dictionary<object, Dictionary<object, object>>();

        private readonly Dictionary<object, object> cache = new Dictionary<object, object>();

        internal TResult GetValue<T, TResult>(
            object funcKey,
            T cachedFuncArgument,
            Func<T, TResult> func)
            where T : ICachedFuncArgument
        {
            var infoOptionalValue = OptionalValue.Nothing<Info>();
            foreach (var argument in cachedFuncArgument.Arguments)
            {
                var closuredArgument = argument;
                infoOptionalValue = new Info(
                    infoOptionalValue.Process(
                        info =>
                            {
                                object value;
                                if (!info.CuurentDictionary.TryGetValue(closuredArgument, out value))
                                {
                                    value = new Dictionary<object, object>();
                                    info.CuurentDictionary.Add(closuredArgument, value);
                                }
                                return (Dictionary<object, object>) value;
                            },
                        () =>
                            {
                                Dictionary<object, object> value;
                                if (!cacheByArguments.TryGetValue(funcKey, out value))
                                {
                                    value = new Dictionary<object, object>();
                                    cacheByArguments.Add(funcKey, value);
                                }
                                return value;
                            }
                        ),
                    argument
                    ).AsOptionalValue();
            }
            return infoOptionalValue.Process(
                info =>
                    {
                        object value;
                        if (!info.CuurentDictionary.TryGetValue(info.PerviousArgument, out value))
                        {
                            value = func(cachedFuncArgument);
                            info.CuurentDictionary.Add(info.PerviousArgument, value);
                        }
                        return (TResult) value;
                    },
                () =>
                    {
                        object value;
                        if (!cache.TryGetValue(funcKey, out value))
                        {
                            value = func(cachedFuncArgument);
                            cache.Add(funcKey, value);
                        }
                        return (TResult) value;
                    }
                );
        }

        private class Info
        {
            private readonly Dictionary<object, object> cuurentDictionary;
            private readonly object perviousArgument;

            public Info(Dictionary<object, object> cuurentDictionary, object perviousArgument)
            {
                this.cuurentDictionary = cuurentDictionary;
                this.perviousArgument = perviousArgument;
            }

            public Dictionary<object, object> CuurentDictionary
            {
                get { return cuurentDictionary; }
            }

            public object PerviousArgument
            {
                get { return perviousArgument; }
            }
        }
    }
}